ROS2 node
We provide a unique ROS2 node to use WOLF in ROS2. This node is generic in the sense that it will serve many different robotics projects without modification. This means that all the user has to do is:
Write a YAML file or a set of YAML files specifying all the problem parameters.
Write a launch file for your application specifying the paths to your YAML files and installed WOLF plugins and subscribers.
Launch and enjoy.
See also
See the YAML configuration section for more information on configuring a ROS2 application with WOLF.
This unique ROS2 node is implemented in the wolf_ros2_node package.
Description of the node
A quick overview of the ROS node follows:
namespace wolf
{
class Node : public rclcpp::Node
{
public:
// subscribers
std::list<SubscriberBasePtr> subscribers_;
// publishers
std::list<PublisherBasePtr> publishers_;
// sensor switcher
SensorSwitcherPtr sensor_switcher_;
protected:
YAML::Node params_; ///< params
ProblemPtr problem_ptr_; ///< wolf problem
SolverManagerPtr solver_; ///< solver
// profiling
bool profiling_;
std::ofstream profiling_file_;
std::chrono::time_point<std::chrono::high_resolution_clock> start_experiment_;
// print
bool print_problem_;
double print_period_;
rclcpp::Time last_print_;
int print_depth_;
bool print_factored_by_, print_metric_, print_state_blocks_;
public:
double node_rate_;
Node();
virtual ~Node(){};
void initialize();
SolverManagerPtr getSolver();
void print();
void createProfilingFile();
};
} // namespace wolf
where the principal bits are briefly described below:
The node constructor & initialize()
The node constructor uses WOLF autoconf to:
Read the path of the main YAML file provided through the ROS parameter
yaml_file_path.Load the YAML file and search the required plugins and packages.
Load all required WOLF plugins and ROS2 packages and store their schema folders for validation.
Validate the YAML configuration against the loaded schemas.
Configure the WOLF problem as specified (dimension, sensors, processors, tree manager, map…)
Configure a solver for the factor graph.
Configure other features such as node rate, profiling options and printing options.
Then, the initialize() method is called to:
Create a series of ROS2 subscribers with callbacks, one per sensor, as specified in the YAML config file.
Create a series of ROS2 publishers with topic and specified frequency in the YAML config file.
Create a sensor switcher to enable/disable sensors on the fly.
solve() & solveLoop()
The solve() method calls the solveLoop() periodically to optimize the factor graph and if specified,
compute the covariance of parts of the state.
It is executed in an dedicated thread with high priority to ensure that the solver is not delayed by other processes.
The main() routine
In ǹode.cpp, you can find the main() routine:
int main(int argc, char **argv)
{
// Init ROS
rclcpp::init(argc, argv);
// Wolf node
std::shared_ptr<wolf::Node> wolf_node = std::make_shared<wolf::Node>();
// Initialize publishers and subscribers
wolf_node->initialize();
rclcpp::Rate loopRate(wolf_node->node_rate_);
// periodic stuff
rclcpp::Time last_check = wolf_node->get_clock()->now();
// Init solver thread
wolf_node->getSolver()->run();
// Init publishers threads
for (auto pub : wolf_node->publishers_)
{
WOLF_INFO("Running publisher ", pub->getType(), "(", pub->getTopic(), ")");
pub->run();
}
while (rclcpp::ok())
{
// check that subscribers received data (every second)
if ((wolf_node->get_clock()->now() - last_check).seconds() > 1)
{
for (auto sub : wolf_node->subscribers_)
WOLF_WARN_COND(sub->secondsSinceLastCallback() > 5,
sub->getType(),
"(",
sub->getTopic(),
")",
" has not received any callback for ",
sub->secondsSinceLastCallback(),
"s.");
last_check = wolf_node->get_clock()->now();
}
// print periodically
wolf_node->print();
// execute pending callbacks
rclcpp::spin_some(wolf_node);
// relax to fit output rate
loopRate.sleep();
}
// Stop solver thread
WOLF_DEBUG("Node is shutting down outside loop... waiting for the solver thread to stop...");
wolf_node->getSolver()->stop();
WOLF_DEBUG("thread stopped.");
// Stop publishers threads
WOLF_DEBUG("waiting for the publishers threads to stop...");
for (auto pub : wolf_node->publishers_) pub->stop();
WOLF_DEBUG("threads stopped.");
// PROFILING
wolf_node->createProfilingFile();
rclcpp::shutdown();
return 0;
}
Some important remarks:
The subscribers are called sequentially in the main thread. Note that inside the subscribers, a capture of the corresponding type will be created and processed.
The publishers have their dedicated threads.